using System;
using System.Collections;
using CI.Interfaces;
using System.Reflection;
using System.Threading;
using System.Runtime.Serialization;

namespace CI.Organism
{
    /// <summary>Cell</summary>
    [DataContract(IsReference = true)]
    public abstract class Cell
    {

        protected static SimEnv SimEnv { get { return SimEnv.Instance; } }

        #region Cell Properties

        /// <summary>Cells guid</summary>
        [DataMember]
        public Guid Guid { get; set; }

        /// <summary>Heart beat Time Stamp of when the cell is Alive(Created)</summary>
        public uint HBTSAlive { get; set; }

        /// <summary>Heart beat Time Stamp of when the cell is Dead(Destroyed)</summary>
        public uint HBTSDead { get; set; }

        /// <summary>The childOrganism the cell belongs to</summary>
        [DataMember]
        public IOrganism Organism { get; set; }

        /// <summary>Is the cell active(alive)</summary>
        public bool Active { get; set; }

        #endregion

        /// <summary>Add the cell to Hosting Environment</summary>
        public virtual void AddCell()
        {

            SimEnv.TraceLogger.LogDebug(string.Format("Start - Adding Cell {0} of type {1} is IPhysical {2} in organism {3}", Guid, GetType(), (this is IPhysical), Organism.Guid));

            // record when the cell was created
            HBTSAlive = Organism.HeartbeatCount;

            // if the cell is phyical wire it into the environment
            if (this is IPhysical)
            {
                if (Organism.HostingEnvironment.Simulation != null)
                {
                    SimEnv.TraceLogger.LogDebug(string.Format("Adding Cell - Restriction checking"));
                    Organism.HostingEnvironment.Simulation.RestrictionChecking(Organism.HostingEnvironment);
                }

                SimEnv.TraceLogger.LogDebug(string.Format("Adding Cell - RegisterPhysicalCell"));
                Organism.Organisms.hostEnvironment.RegisterPhysicalCell((ICell)this, ActionType.AddCell);
                Organism.RegistrationChange();
                Organism.BornChange();

                // make sure it got registered if so it is active
                Active = Organism.HostingEnvironment.HostingEnvironmentPhysical.Registered(Organism);
            }
            else
            {
                // mark the cell as active
                Active = true;
            }

            SimEnv.TraceLogger.LogDebug(string.Format("Adding Cell - Active :{0}, Registered :{1}", Active, Organism.HostingEnvironment.HostingEnvironmentPhysical.Registered(Organism)));
            SimEnv.TraceLogger.LogDebug(string.Format("Finished - Adding Cell {0} in organism {1}", Guid, Organism));
        }

        /// <summary>Delete the cell from the hosting Environment</summary>
        public virtual void DelCell()
        {
            // make sure the cell is active first
            if (!(Active))
            {
                Organism.Terminate("Attemping to delete a not yet active cell");
                return;
            }

            // record when the cell was deleted
            HBTSDead = Organism.HeartbeatCount;

            // if the cell is phyical wire it into the environment
            if (this is IPhysical)
            {
                if (SimEnv.Instance.UsingHW)
                {
                    const string message = "Organism has died, restarting scenario";
                    
                    SimEnv.TraceLogger.LogDebug(message);
                    
                    if (SimEnv.HostEnv != null)
                        SimEnv.HostEnv.ProcessingState = message;

                    SimEnv.TriggerScenarioRestart(message);
                    return;
                }
                // kill the childOrganism
                if (Monitor.TryEnter(Organism.Organisms, Globals.CriticalAcquireLockTimeout))
                {
                    try
                    {
                        // remove it from the alive list
                        Organism.Organisms.AliveRemove(Organism);
                        // add it to the dead list
                        Organism.Organisms.DeadAdd(Organism);
                        // let them know its out of the loop

                        SimEnv.TraceLogger.LogDebug(string.Format("Organism {0} has died", Organism.Guid));
                        Organism.Organisms.hostEnvironment.RegisterPhysicalCell((ICell)this, ActionType.DelCell);
                        Organism.RegistrationChange();
                    }
                    finally
                    {
                        Monitor.Exit(Organism.Organisms);
                    }
                }
                else
                {
                    throw new CellException("Unable to acquire lock on Organism.Organisms");
                }
            }

            // mark the cell as not active
            Active = false;
        }

        /// <summary>
        /// Create the cell in the childOrganism
        /// </summary>
        public Cell(IOrganism organism, Guid guid)
        {
            Active = false;

            #region Argument check
            if (organism == null)
                throw new ArgumentException("Organism");
            if (guid == Guid.Empty)
                throw new ArgumentException("guid");
            #endregion

            // save the cell guid
            Guid = guid;

            // add the cell to the childOrganism cell colleciton
            Organism = organism;
            Organism.Cells.Add((ICell)this);

            // set the cell as intiall inactive
            Active = false;
        }

        /// <summary>
        /// Take this parent cell and create a new cell to be put in the new child Organism
        /// Mutate the current cell and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child child Organism it will be going into</param>
        /// <returns>new cell for child</returns>
        public abstract ICell Mutate(double mutationRate, IOrganism childOrganism);

        /// <summary>
        /// Create a new cell
        /// </summary>
        /// <param name="mutationRate">mutation rate</param>
        /// <param name="childOrganism">child Organism it will be created in</param>
        /// <returns>new cell</returns>
        public static ICell CreateNewCell(double mutationRate, IOrganism childOrganism)
        {
            #region Argument check
            if (childOrganism == null)
                throw new ArgumentException("Organism");
            #endregion

            // mutate adding new cells
            // we need to reflect and see what genes are available
            ArrayList cells = Globals.GetConcreteClasses(typeof(ICell));
            // now create a new one
            // class type
            var organismAlreadyHasZygote = childOrganism.Zygote() != null;
            Type classType;
            do
            {
                // save its type
                classType = (Type)cells[Mutation.CollectionSelect(cells)];
                // make sure its a class and its not abstract
                // also make sure if their is no brain that it is not a neural cell
            } while
                (((!classType.IsClass)
                || (classType.IsAbstract))
                || (typeof(IPhysical).IsAssignableFrom(classType) && organismAlreadyHasZygote)
                || (typeof(INeuron).IsAssignableFrom(classType) && (childOrganism.NeuralSystem == null)));



            // We are anticipating a constructor that takes 1 parameters,
            var conTypes = new Type[2];
            conTypes[0] = typeof(IOrganism);
            conTypes[1] = typeof(Guid);
            // Get the constructor that adheres to our parameters.
            ConstructorInfo constructor = classType.GetConstructor(conTypes);
            // Define the parameters to pass into our constructor when we call it.
            var constructorParams = new object[2];
            constructorParams[0] = childOrganism;
            constructorParams[1] = Guid.NewGuid();

            SimEnv.Instance.TraceLogger.LogDebug(string.Format("Invoking constructor of type {0}", classType));

            // Invoke the constructor dynamically, getting an instance of the class.  
            var newCell = constructor.Invoke(constructorParams) as ICell;

            // mutate the new cell if its not a base class cell
            if (!(newCell.GetType().Equals(typeof(Cell))))
            {
                // remove the one we just proir
                childOrganism.Cells.Remove(newCell);
                //mutate and add
                newCell = newCell.Mutate(mutationRate, childOrganism);
            }

            return newCell;
        }

        /// <summary>
        /// Cell exception
        /// </summary>
        public class CellException : Exception
        {
            /// <summary>
            /// Cell Exception message
            /// </summary>
            /// <param name="message"></param>
            public CellException(string message) : base(message) { }
        }

        /// <summary>
        /// do a equals comparison of one cell type to another only comparing the value types, no refference comparisons
        /// </summary>
        /// <param name="cell">cell to compare against</param>
        /// <returns>true if equal value types</returns>
        public virtual bool EqualsValueTypes(ICell cell)
        {
            // make sure we have something to compare against
            if (cell == null)
                return false;
            // compare the types
            if (!GetType().Equals(cell.GetType()))
                return false;
            // get the cell to do a selective comparison
            if (cell.Guid != Guid)
                return false;
            if (cell.HBTSAlive != HBTSAlive)
                return false;
            if (cell.HBTSDead != HBTSDead)
                return false;
            return true;
        }

        /// <summary>
        /// To String
        /// </summary>
        /// <param name="value"></param>
        public abstract string ToString(string value);

    }
}
